View Javadoc

1   /*
2    * Copyright (C) 1998-2000 Semiotek Inc.  All Rights Reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted under the terms of either of the following
6    * Open Source licenses:
7    *
8    * The GNU General Public License, version 2, or any later version, as
9    * published by the Free Software Foundation
10   * (http://www.fsf.org/copyleft/gpl.html);
11   *
12   *  or
13   *
14   * The Semiotek Public License (http://webmacro.org/LICENSE.)
15   *
16   * This software is provided "as is", with NO WARRANTY, not even the
17   * implied warranties of fitness to purpose, or merchantability. You
18   * assume all risks and liabilities associated with its use.
19   *
20   * See www.webmacro.org for more information on the WebMacro project.
21   */
22  
23  
24  package org.webmacro.engine;
25  
26  import org.webmacro.*;
27  import org.webmacro.util.Named;
28  
29  import java.io.IOException;
30  
31  // PRIMARY CLASS: Variable
32  
33  /***
34   * A Variable is a reference into a Propertymap.
35   * <p>
36   * A variable name contains a list of names separated by dots, for
37   * example "$User.Identity.email.address" is the list: User, Identity,
38   * email, and address.
39   * <p>
40   * PLEASE NOTE: Case-sensitivity is enforced. "User" is the the same
41   * name as "user".
42   * <p>
43   * What that means: When a template is interpreted, it is interpreted
44   * in terms of data in a hashtable/map called the "context". This is
45   * actually a Map of type Map. The context contains all the
46   * local variables that have been set, as well as other information
47   * that Macros may use to evaluate the request.
48   * <p>
49   * Variable depends heavily on Property introspection: It is defined
50   * as a list of one or more names (separated by dots when written).
51   * <p>
52   * Those names are references to sub-objects within the context. The
53   * Variable instance, when interpreted, will decend through the context
54   * following fields, method references, or hash table look-ups based
55   * on its names.
56   * <p>
57   * For example, the variable "$User.Identity.email.address" implies
58   * that there is a "User" object under the Map--either it is
59   * a field within the map, or the map has a getUser() method, or
60   * the User can be obtained by calling Map.get("User").
61   * <p>
62   * The full expansion of $User.Identity.email.address might be:<pre>
63   *
64   *    Map.get("User").getIdentity().get("email").address
65   *
66   * </pre>. Variable (actually the Property class it uses) will figure
67   * out how to decend through the object like this until it finds the
68   * final reference--which is the "value" of the variable.
69   * <p>
70   * When searchin for subfields Variable prefers fields over getFoo()
71   * methods, and getFoo() over get("Foo").
72   *
73   */
74  public abstract class Variable implements Macro, Visitable
75  {
76  
77  
78      // null: because in BuildContext.getVariableType() we can just
79      // return null from a HashMap for a never before heard of variable
80      // to mean that it is a PROPERTY_TYPE. Only this code right here
81      // and BuildContext.getVariableType() needs to know that.
82      final static public Object PROPERTY_TYPE = new Object();
83      final static public Object LOCAL_TYPE = new Object();
84  
85      /***
86       * The name of this variable.
87       */
88      private String _vname;
89  
90      /***
91       * The name as an array
92       */
93      protected Object[] _names;
94  
95      /***
96       * Create a variable with the supplied name. The elements of the name
97       * are either strings, or a method reference.
98       */
99      Variable (Object names[])
100     {
101         _names = names;
102 
103     }
104 
105     /***
106      * Return the property names for this variable. These are stringified
107      * names corresponding to the names of the variable; if one of the
108      * elements of the variable name is a method call then the name of
109      * the method is inserted at that point as if it were a property name.
110      */
111     static final String[] makePropertyNames (Object names[])
112     {
113         String[] sn = new String[names.length];
114         for (int i = 0; i < sn.length; i++)
115         {
116             sn[i] = (names[i] instanceof Named) ?
117                     ((Named) names[i]).getName() : (String) names[i];
118         }
119         return sn;
120     }
121 
122     public final String[] getPropertyNames ()
123     {
124         return makePropertyNames(_names);
125     }
126 
127     /***
128      * Like getPropertyNames, but only works if isSimpleName is true
129      */
130     public final String getName ()
131     {
132         return (_names[0] instanceof Named) ?
133                 ((Named) _names[0]).getName()
134                 : (String) _names[0];
135     }
136 
137     /***
138      * Returns true if the Variable describes a simple name (one with only
139      * one element)
140      */
141     public boolean isSimpleName ()
142     {
143         return (_names.length == 1);
144     }
145 
146     /***
147      * Looks in the hashTable (context) for a value keyed to this variables
148      * name and returns the value string. If the resulting value is a Macro,
149      * recursively call its evaluate method.
150      * @return String
151      */
152     final public Object evaluate (Context context) throws PropertyException
153     {
154         try
155         {
156             Object val = getValue(context);
157             if (val instanceof Macro)
158             {
159                 val = ((Macro) val).evaluate(context); // recurse
160             }
161             return val;
162         }
163         catch (NullPointerException e)
164         {
165             // May throw
166             context.getEvaluationExceptionHandler()
167                     .evaluate(this, context,
168                               new PropertyException.NullValueException(getVariableName()));
169             return null;
170         }
171         catch (PropertyException e)
172         {
173             // May throw
174             if (e instanceof PropertyException.UndefinedVariableException)
175             {
176                 PropertyException.UndefinedVariableException uve = (PropertyException.UndefinedVariableException) e;
177                 if (_names.length > 1)
178                     uve.setMessage(
179                             "Attempted to reference a property or method of an undefined variable: $" + _names[0]);
180                 else
181                     uve.setMessage(
182                             "Attempted to evaluate an undefined variable: $" + _names[0]);
183             }
184             context.getEvaluationExceptionHandler()
185                     .evaluate(this, context, e);
186             return null;
187         }
188         catch (Exception e)
189         {
190             // May throw
191             context.getEvaluationExceptionHandler()
192                     .evaluate(this, context,
193                             new PropertyException("Variable: exception evaluating "
194                                                   + getVariableName(), e));
195             return null;
196         }
197     }
198 
199     /***
200      * Look in the hashtable (context) for a value keyed to this variables
201      * name and write its value to the stream.
202      * @exception PropertyException is required data is missing
203      * @exception IOException if could not write to output stream
204      */
205     final public void write (FastWriter out, Context context)
206             throws PropertyException, IOException
207     {
208         try
209         {
210             Object val = getValue(context);
211             if (val instanceof Macro)
212                 ((Macro) val).write(out, context);
213             else
214             {
215                 if (val != null)
216                 {
217                     String v = val.toString();
218                     if (v != null)
219                         out.write(v);
220                     else
221                     {
222                         out.write(context.getEvaluationExceptionHandler()
223                                 .expand(this, context,
224                                         new PropertyException.NullToStringException(getVariableName())));
225                     }
226                 }
227                 else
228                 {
229                     if (isSimpleName())
230                     {
231                         // user accessed a variable that isn't in the context
232                         //     $ObjectNotInContext
233                         out.write(context.getEvaluationExceptionHandler()
234                                 .expand(this, context,
235                                         new PropertyException.NoSuchVariableException(getVariableName())));
236                     }
237                     else
238                     {
239                         // user accessed a valid property who's value is null
240                         out.write(context.getEvaluationExceptionHandler()
241                                 .expand(this, context,
242                                         new PropertyException.NullValueException(getVariableName())));
243                     }
244                 }
245             }
246         }
247         catch (PropertyException e)
248         {
249             if (e instanceof PropertyException.UndefinedVariableException)
250             {
251                 PropertyException.UndefinedVariableException uve = (PropertyException.UndefinedVariableException) e;
252                 if (_names.length > 1)
253                     uve.setMessage(
254                             "Attempted to write a property or method value of an undefined variable: $" + _names[0]);
255                 else
256                     uve.setMessage(
257                             "Attempted to write an undefined variable: $" + _names[0]);
258             }
259             out.write(context.getEvaluationExceptionHandler()
260                     .expand(this, context, e));
261         }
262         catch (Exception e)
263         {
264             // something we weren't expecting happened!
265             // I wonder if we would ever get here?  --eric
266             out.write(context.getEvaluationExceptionHandler()
267                     .expand(this, context, e));
268         }
269     }
270 
271     /***
272      * Helper method to construct a String name from a Object[] name
273      */
274     final static String makeName (Object[] names)
275     {
276         StringBuffer buf = new StringBuffer();
277         for (int i = 0; i < names.length; i++)
278         {
279             if (i != 0)
280             {
281                 buf.append(".");
282             }
283             buf.append(names[i]);
284         }
285         return buf.toString();
286     }
287 
288     /***
289      * The code to get the value represented by the variable from the
290      * supplied context.
291      */
292     public abstract Object getValue (Context context) throws PropertyException;
293 
294     /***
295      * The code to set the value represented by the variable in the
296      * supplied context.
297      */
298     public abstract void setValue (Context c, Object v) throws PropertyException;
299 
300     /***
301      * Return the String name of the variable prefixed with a string
302      * representing its type. For example local:a.b.c
303      */
304     public abstract String toString ();
305 
306     /***
307      * Return the canonical name for this variable
308      */
309     public synchronized String getVariableName ()
310     {
311       if (_vname == null) {
312         _vname = makeName(_names).intern();
313       }
314       return _vname;
315     }
316 
317     public void accept (TemplateVisitor v)
318     {
319         v.visitVariable(this, _names);
320     }
321 
322 }
323 
324 
325